﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System.Collections.Generic;
using AAEngine.Engine.Core;
using AAEngine.Engine.Debug;
using AAEngine.Engine.Resources;

namespace AAEngine.Sound
{
    public class SoundManager : ISoundManager, ITickedObject
    {
        public static readonly string MUSIC_MIXER_CATEGORY = "music";
        public static readonly string SFX_MIXER_CATEGORY = "sfx";

        protected Dictionary<string, SoundCategory> _categories = new Dictionary<string,SoundCategory>();
        protected SoundCategory _rootCategory = new SoundCategory();
        protected List<ISoundHandle> _playingSounds = new List<ISoundHandle>();

        public int MaxConcurrentSounds
        {
            get { return _maxConcurrentSounds; }
            set { _maxConcurrentSounds = value; }
        }
        private int _maxConcurrentSounds = 5;

        public SoundManager()
        {
            CreateCategory(MUSIC_MIXER_CATEGORY);
            CreateCategory(SFX_MIXER_CATEGORY);
        }

#if WINDOWS_PHONE || XBOX
        public ISoundHandle Play(SoundResource sound)
        {
            return Play(sound, SoundManager.SFX_MIXER_CATEGORY, 0, 0, 0);
        }

        public ISoundHandle Play(SoundResource sound, string category)
        {
            return Play(sound, category, 0, 0, 0);
        }

        public ISoundHandle Play(SoundResource sound, string category, float pan)
        {
            return Play(sound, category, pan, 0, 0);
        }

        public ISoundHandle Play(SoundResource sound, string category, float pan, int loopCount)
        {
            return Play(sound, category, pan, loopCount, 0);
        }

        public ISoundHandle Play(SoundResource sound, string category, float pan, int loopCount, float startDelay)
#else
        public ISoundHandle Play(SoundResource sound, string category = "sfx", float pan = 0, int loopCount = 0, float startDelay = 0)
#endif
        {
            Profiler.Enter("SoundManager.Play");

            if (_playingSounds.Count > MaxConcurrentSounds)
            {
                Profiler.Exit("SoundManager.Play");
                return null;
            }

            SoundHandle sh = new SoundHandle(this, sound, category, pan, loopCount, startDelay);
            SoundCategory sc = _categories[category];
            sh.Update(SoundCategory.ApplyCategoriesToTransform(sc.Muted, sh.Pan, sh.Volume, sc));

            _playingSounds.Add(sh);

            Profiler.Exit("SoundManager.Play");
            return sh;
        }

        public bool Muted
        {
            get { return _rootCategory.Muted; }
            set
            {
                _rootCategory.Muted = value;
                _rootCategory.IsDirty = true;
            }
        }

        public float Volume
        {
            get { return _rootCategory.Volume; }
            set
            {
                _rootCategory.Volume = value;
                _rootCategory.IsDirty = true;
            }
        }

        public void CreateCategory(string category)
        {
            _categories.Add(category, new SoundCategory());
        }

        public void RemoveCategory(string category)
        {
            _categories.Remove(category);
        }

        public void SetCategoryMuted(string category, bool value)
        {
            _categories[category].Muted = value;
            _categories[category].IsDirty = true;
        }

        public bool GetCategoryMuted(string category)
        {
            return _categories[category].Muted;
        }

        public void SetCategoryVolume(string category, float value)
        {
            _categories[category].Volume = value;
            _categories[category].IsDirty = true;
        }

        public float GetCategoryVolume(string category)
        {
            return _categories[category].Volume;
        }

        public void StopCategorySounds(string category)
        {
            for (int i = _playingSounds.Count - 1; i >= 0; i--)
            {
                if (_playingSounds[i].Category != category)
                {
                    continue;
                }

                _playingSounds[i].Stop();
            }
        }

        public void StopAll()
        {
            for (int i = _playingSounds.Count; i >= 0; i--)
            {
                _playingSounds[i].Stop();
            }
        }

        public void GetSoundHandlesInCategory(string category, ref IList<ISoundHandle> outArray)
        {
            for (int i = _playingSounds.Count - 1; i >= 0; i--)
            {
                if (_playingSounds[i].Category != category)
                {
                    continue;
                }

                outArray.Add(_playingSounds[i]);
            }
        }

        protected void UpdateSounds()
        {
            Profiler.Enter("SoundManager.UpdateSounds");

            // Push dirty state down.
            if (_rootCategory.IsDirty == false)
            {
                // Each category must dirty its sounds.
                foreach (string categoryName in _categories.Keys)
                {
                    // Skip clean.
                    if (_categories[categoryName].IsDirty == false)
                    {
                        continue;
                    }

                    // OK, mark appropriate sounds as dirty.
                    for (int j = 0; j < _playingSounds.Count; j++)
                    {
                        SoundHandle csh = _playingSounds[j] as SoundHandle;

                        if (csh.Category != categoryName)
                        {
                            continue;
                        }

                        csh.IsDirty = true;
                    }

                    // Clean the state.
                    _categories[categoryName].IsDirty = false;
                }
            }
            else
            {
                // Root state is dirty, so we can clean all the categories.
                foreach (string categoryName in _categories.Keys)
                {
                    _categories[categoryName].IsDirty = false;
                }
            }

            for (int i = _playingSounds.Count - 1; i >= 0; i--)
            {
                SoundHandle csh = _playingSounds[i] as SoundHandle;

                if (csh.IsDone)
                {
                    csh.Stop();
                    continue;
                }

                if (csh.IsDirty == false && _rootCategory.IsDirty == false)
                {
                    continue;
                }

                float pan = csh.Pan + _rootCategory.Pan + _categories[csh.Category].Pan;
                float volume = csh.Volume * _rootCategory.Volume * _categories[csh.Category].Volume;
                bool muted = _rootCategory.Muted || _categories[csh.Category].Muted;

                csh.Update(SoundCategory.ApplyCategoriesToTransform(false, csh.Pan, csh.Volume, _rootCategory, _categories[csh.Category]));

                // Clean it.
                csh.IsDirty = false;
            }

            // Clean the root category.
            _rootCategory.IsDirty = false;

            Profiler.Exit("SoundManager.UpdateSounds");
        }

        public void OnTick(float elapsed)
        {
            UpdateSounds();
        }

        internal bool IsInPlayingSounds(SoundHandle sh)
        {
            return _playingSounds.Contains(sh);
        }

        internal void RemoveSoundHandle(SoundHandle sh)
        {
            _playingSounds.Remove(sh);
        }
    }
}
